#include <iostream>
#include <vector>
#include <sstream>
#include <fstream>
#include <cmath>
#include <cstdlib>

using namespace std;

template<typename T>
T* mallocArray(int n) {
	T* arr = (T*)malloc(n * sizeof(T));
	return arr;
}
template<typename T>
T** mallocArray2D(int row, int col) {
	T** arr = (T**)malloc(row * sizeof(T*));
	arr[0] = (T*)malloc(row * col * sizeof(T));
	for (int i = 1; i < row; i++) {
		arr[i] = arr[i - 1] + col;
	}
	return arr;
}
template<typename T>
void freeArray(T* arr) {
	if(arr != NULL){
        free(arr);
	}
}
template<typename T>
void freeArray2D(T** arr) {
	if(arr != NULL){
        free(arr[0]);
        free(arr);
    }
}

class MyLR {
public:
	MyLR(string trainFile, string testFile, string predictOutFile);
	~MyLR();
	void train();
	void predict();
private:
	bool loadTrainData();
	void initParam();
	bool init();
	void compute(const float* data, const int label);
	bool loadTestData();
	int storePredict();

private:
	float** trainDatas = NULL;
	float** testDatas = NULL;
	int* trainLabels = NULL;
	int* predictLabels = NULL;
	float* weights = NULL;
	string trainFile;
	string testFile;
	string predictOutFile;
private:
	int trainSimpleNum = 0;
	int testSimpleNum = 0;
	int featuresNum = 0;
	const float wtInitV = 1.0;
	const float stepSize = 0.04;
	const int maxIterTimes = 50;
	const float predictTrueThresh = 0.5;
};

MyLR::MyLR(string trainF, string testF, string predictOutF){
	trainFile = trainF;
	testFile = testF;
	predictOutFile = predictOutF;
	init();
}

MyLR::~MyLR() {
    freeArray2D(trainDatas);
    freeArray(trainLabels);
    freeArray2D(testDatas);
    freeArray(predictLabels);
    freeArray(weights);
}

bool MyLR::loadTrainData(){
	ifstream infile(trainFile, ios::binary);
	if (!infile) {
		exit(0);
	}
	unsigned int size = static_cast<unsigned int>(infile.seekg(0, std::ios::end).tellg());
	char *buf = new char[size];
	infile.seekg(0, ios::beg).read(&buf[0], size);
	infile.close();
	char* p = buf;
	featuresNum = 0;
	trainSimpleNum = 0;
	while (*p != '\0')
	{
		if ((trainSimpleNum == 0) && (*p == ',')) {
			featuresNum++;
		}
		else if(*p == '\n') {
			trainSimpleNum++;
		}
		p++;
	}
	trainLabels = mallocArray<int>(trainSimpleNum);
	trainDatas = mallocArray2D<float>(trainSimpleNum, featuresNum);
	float tmp = 0;
	float sign = 1.0;
	float step = 0.1;
	p = buf;
	for (int i = 0; i < trainSimpleNum; i++) {
		for (int j = 0; j < featuresNum; j++) {
			if (*p == '-') { sign = -1.0, p++; }
			while (*p != ',' && *p != '.')
			{
				tmp *= 10;
				tmp += (*p) - '0';
				p++;
			}
			if (*p == '.')
			{
				step = 0.1;
				p++;
				while (*p !=',') {
					tmp += step * ((*p) - '0');
					step /= 10;
					p++;
				}
			}
			trainDatas[i][j] = sign * tmp;
			tmp = 0;
			sign = 1.0;
			p++;
		}
		trainLabels[i] = *(p++)-'0';
		p++;
	}
	delete[] buf;
	return true;
}

void MyLR::initParam() {
	weights = mallocArray<float>(featuresNum);
	for (int i = 0; i < featuresNum; i++) {
		weights[i] = wtInitV;
	}
}

bool MyLR::init() {
	bool status = loadTrainData();
	if (status != true) {
		return false;
	}
	initParam();
	return true;
}

void MyLR::compute(const float* data, const int label){
    int i;
    float sigmoidVal = 0;
    for(i=0; i<featuresNum; i++){
        sigmoidVal += data[i] * weights[i];
    }
    sigmoidVal = 1 / (1 + exp(-sigmoidVal));
    float loss = pow(label - sigmoidVal, 2);
    for(i=0; i<featuresNum; i++){
        weights[i] -= stepSize * data[i] * (sigmoidVal - label);
    }
}

void MyLR::train() {
	int i, j;
	for (i = 0; i < maxIterTimes; i++) {
		for(j=0; j<trainSimpleNum; j++){
            compute(trainDatas[j], trainLabels[j]);
		}
	}
}

void MyLR::predict() {
    int i, j;
	float predictVal;
	loadTestData();
	for (i = 0; i < testSimpleNum; i++) {
        predictVal = 0.0;
        for(j=0; j<featuresNum; j++){
            predictVal += testDatas[i][j] * weights[j];
        }
		predictLabels[i] = predictVal >= predictTrueThresh ? 1 : 0;
	}
	storePredict();
}

bool MyLR::loadTestData() {
	ifstream infile(testFile, ios::binary);
	if (!infile) {
		exit(0);
	}
	unsigned int size = static_cast<unsigned int>(infile.seekg(0, std::ios::end).tellg());
	char* buf = new char[size];
	infile.seekg(0, ios::beg).read(&buf[0], size);
	infile.close();
	char* p = buf;
	featuresNum = 0;
	testSimpleNum = 0;
	while (*p != '\0')
	{
		if ((testSimpleNum == 0) && (*p == ',')) {
			featuresNum++;
		}
		else if (*p == '\n') {
			testSimpleNum++;
		}
		p++;
	}
	featuresNum++;
	predictLabels = mallocArray<int>(testSimpleNum);
	testDatas = mallocArray2D<float>(testSimpleNum, featuresNum);
	float tmp = 0;
	float sign = 1.0;
	float step = 0.1;
	p = buf;
	for (int i = 0; i < testSimpleNum; i++) {
		for (int j = 0; j < featuresNum; j++) {
			if (*p == '-') { sign = -1.0, p++; }
			while (*p != ',' && *p != '.' && *p != '\n')
			{
				tmp *= 10;
				tmp += (*p) - '0';
				p++;
			}
			if (*p == '.')
			{
				step = 0.1;
				p++;
				while (*p != ',' && *p != '\n') {
					tmp += step * ((*p) - '0');
					step /= 10;
					p++;
				}
			}
			testDatas[i][j] = sign * tmp;
			tmp = 0;
			sign = 1.0;
			p++;
		}
	}
	delete[] buf;
	return true;
}

int MyLR::storePredict() {
	string line;
	int i;

	ofstream fout(predictOutFile);
	if (!fout.is_open()) {
		exit(0);
	}
	for (i = 0; i < testSimpleNum; i++) {
		fout << predictLabels[i] << endl;
	}
	fout.close();
	return 0;
}

bool loadAnswerData(string awFile, vector<int>& awVec){
	ifstream infile(awFile.c_str());
	if (!infile) {
		exit(0);
	}

	while (infile) {
		string line;
		int aw;
		getline(infile, line);
		if (line.size() > 0) {
			stringstream sin(line);
			sin >> aw;
			awVec.push_back(aw);
		}
	}

	infile.close();
	return true;
}

int main(int argc, char* argv[]){
	string trainFile = "/data/train_data.txt";
    string testFile = "/data/test_data.txt";
    string predictFile = "/projects/student/result.txt";
    string answerFile = "/projects/student/answer.txt";

	MyLR logist(trainFile, testFile, predictFile);
	logist.train();
	logist.predict();
	return 0;
}
